//
// WinPcap.net
//
// Copyright (C) 2005 Matthew Ward
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
// Matthew Ward (mrward@users.sourceforge.net)

using System;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Text;

namespace WinPcap
{
	/// <summary>
	/// An IP data packet.
	/// </summary>
	public sealed class IPPacket : Packet
	{
		/// <summary>
		/// The byte index position of the Internet Protocol version.
		/// </summary>
		public const int VersionPosition = 0;

		/// <summary>
		/// The byte index position of the Internet Protocol header length.
		/// </summary>
		public const int HeaderLengthPosition = 0;

		/// <summary>
		/// The byte index position of the packet's destination IP address.
		/// </summary>
		public const int DestinationPosition = 16;

		/// <summary>
		/// The byte index position of the packet's source IP address.
		/// </summary>
		public const int SourcePosition = 12;

		/// <summary>
		/// The byte index position of the protocol information.
		/// </summary>
		public const int ProtocolPosition = 9;

		/// <summary>
		/// The length of an IP address in bytes.
		/// </summary>
		public const int IPAddressLength = 4;

		/// <summary>
		/// The byte index position of the time to live.
		/// </summary>
		public const int TimeToLivePosition = 8;

		/// <summary>
		/// The minimum IP header length in bytes.
		/// </summary>
		public const int MinimumHeaderLength = 20;
		
		/// <summary>
		/// The Internet protocol version.
		/// </summary>
		int m_Version;

		/// <summary>
		/// The IP packet's header length in bytes.
		/// </summary>
		int m_HeaderLength;

		/// <summary>
		/// Gets the protocol used in this data packet.
		/// </summary>
		Protocol m_Protocol;

		/// <summary>
		/// The packet's destination IP address.
		/// </summary>
		IPAddress m_Destination;

		/// <summary>
		/// The packet's source IP address.
		/// </summary>
		IPAddress m_Source;

		/// <summary>
		/// The packet's time to live.
		/// </summary>
		byte m_TimeToLive;

		/// <summary>
		/// The packet contained inside this one.
		/// </summary>
		Packet m_InnerPacket;

		/// <summary>
		/// Creates a new instance of the <see cref="IPPacket"/> class.
		/// </summary>
		/// <param name="bytes">The bytes of the packet.</param>
		public IPPacket( byte[ ] bytes )
			: this( bytes, 0, bytes.Length )
		{
		}
			
		/// <summary>
		/// Creates a new instance of the <see cref="IPPacket"/> class.
		/// </summary>
		/// <param name="bytes">The bytes of the packet.</param>
		/// <param name="startIndex">The index that the data
		/// packet starts from in the byte array.</param>
		public IPPacket( byte[ ] bytes, int startIndex )
			: this( bytes, startIndex, bytes.Length - startIndex )
		{
		}
			
		/// <summary>
		/// Creates a new instance of the <see cref="IPPacket"/> class.
		/// </summary>
		/// <param name="bytes">The bytes of the packet.</param>
		/// <param name="startIndex">The index that the data
		/// packet starts from in the byte array.</param>
		/// <param name="length">The amount of bytes in the byte array
		/// that make up the packet.</param>
		public IPPacket( byte[ ] bytes, int startIndex, int length )
			: base( bytes, startIndex, length )
		{
			m_Version = -1;
			m_Protocol = Protocol.None;
			Parse( GetBytes( ) );
		}
			
		/// <summary>
		/// Converts a packet into an IPPacket.
		/// </summary>
		/// <param name="packet">The packet to convert.</param>
		/// <returns>An IPPacket if the packet can be converted,
		/// otherwise <see langword="null"/>.</returns>
		public static IPPacket GetIPPacket( Packet packet )
		{
			EthernetPacket UnderlyingEthernetPacket = new EthernetPacket( packet.GetBytes( ) );
			IPPacket ConvertedIPPacket = UnderlyingEthernetPacket.InnerPacket as IPPacket;
			return ConvertedIPPacket;
		}

		/// <summary>
		/// The IP version.
		/// </summary>
		/// <remarks>
		/// <para>
		/// <list type="table">
		///		<listheader>
		///			<term>Version</term>
		///			<description>Description</description>
		///		</listheader>
		///		<item>
		///         <term>0</term>
		///			<description>Reserved</description>
		///     </item>
		///		<item>
		///         <term>4</term>
		///			<description>IP, Internet Protocol</description>
		///     </item>
		///		<item>
		///         <term>5</term>
		///			<description>ST, ST Datagram Mode</description>
		///     </item>
		///		<item>
		///         <term>6</term>
		///			<description>SIP, Simple Internet Protocol. 
		///         SIPP, Simple Internet Protocol Plus. 
		///			IPv6, Internet Protocol.</description>
		///     </item>
		///		<item>
		///         <term>7</term>
		///			<description>TP/IX, The Next Internet</description>
		///     </item>
		///		<item>
		///         <term>8</term>
		///			<description>PIP, The P Internet Protocol</description>
		///     </item>
		///		<item>
		///         <term>9</term>
		///			<description>TUBA</description>
		///     </item>
		///		<item>
		///         <term>15</term>
		///			<description>Reserved.</description>
		///     </item>
		///		</list>
		/// </para>
		/// </remarks>
		public int Version
		{
			get
			{
				return m_Version;
			}
		}

		/// <summary>
		/// Gets the IP packet's header length in bytes.
		/// </summary>
		public int HeaderLength
		{
			get
			{
				return m_HeaderLength;
			}
		}
			

		/// <summary>
		/// Gets the protocol used in this packet.
		/// </summary>
		public Protocol Protocol
		{
			get
			{
				return m_Protocol;
			}
		}

		/// <summary>
		/// Gets the packet's source IP address.
		/// </summary>
		public IPAddress Source
		{
			get
			{
				return m_Source;
			}
		}

		/// <summary>
		/// Gets the packet's destination IP address.
		/// </summary>
		public IPAddress Destination
		{
			get
			{
				return m_Destination;
			}
		}

		/// <summary>
		/// The packet's time to live.
		/// </summary>
		public byte TimeToLive
		{
			get
			{
				return m_TimeToLive;
			}
		}

		/// <summary>
		/// Gets the packet contained inside this one.
		/// </summary>
		public override Packet InnerPacket
		{
			get
			{
				return GetInnerPacket( );
			}
		}

		/// <summary>
		/// Gets the packet contained inside this one.
		/// </summary>
		Packet GetInnerPacket( )
		{
			if( m_InnerPacket == null )
			{
				if( m_Protocol == Protocol.Tcp )
				{
					byte[ ] PacketBytes = GetBytes( );
	
					if( PacketBytes.Length > m_HeaderLength )
					{
						// Packet contains a payload.
	
						m_InnerPacket = new TcpPacket( this, 
							PacketBytes, 
							m_HeaderLength );
					}
				}
			}
	
			return m_InnerPacket;
		}

		/// <summary>
		/// Parses the IP data packet.
		/// </summary>
		/// <param name="bytes">The bytes to parse.</param>
		void Parse( byte[ ] bytes )
		{
			GetVersion( bytes );
			GetHeaderLength( bytes );
	
			// Extract more data if packet is not truncated.
	
			if( bytes.Length >= IPPacket.MinimumHeaderLength )
			{
				GetProtocol( bytes );
				m_Source = GetIPAddress( bytes, IPPacket.SourcePosition );
				m_Destination = GetIPAddress( bytes, IPPacket.DestinationPosition );
	
				m_TimeToLive = bytes[ IPPacket.TimeToLivePosition ];
			}
		}

		/// <summary>
		/// Extracts the Internet protocol version from the header.
		/// </summary>
		/// <param name="bytes">The bytes to extract the value from.</param>
		void GetVersion( byte[ ] bytes )
		{
			Debug.Assert( bytes.Length > IPPacket.VersionPosition,
				"Not enough packet bytes to extract IP version" );

			byte VersionByte = bytes[ IPPacket.VersionPosition ];

			// Extract high order part of byte.

			m_Version = Packet.GetHighOrderNibble( VersionByte );
			
		}

		/// <summary>
		/// Gets the Internet Header length value in bytes.
		/// </summary>
		/// <param name="bytes">The bytes to extract the value from.</param>
		void GetHeaderLength( byte[ ] bytes )
		{
			byte HeaderLengthByte = bytes[ IPPacket.HeaderLengthPosition ];
	
			// Extract high order part of byte.
			int HighOrderPart = Packet.GetLowOrderNibble( HeaderLengthByte );
	
			// The Internet header length gives the length in as 
			// a number of 32 bit words so we need to multiply this by
			// 4 to get the number of bytes.
	
			m_HeaderLength = Packet.BytesIn32Bits * HighOrderPart;
		}

		/// <summary>
		/// Gets the protocol information.
		/// </summary>
		/// <param name="bytes">The bytes to extract the value from.</param>
		void GetProtocol( byte[ ] bytes )
		{
			Debug.Assert( bytes.Length > IPPacket.ProtocolPosition,
				"Not enough packet bytes to extract IP protocol" );
	
			byte ProtocolByte = bytes[ IPPacket.ProtocolPosition ];
	
			m_Protocol = ( Protocol )ProtocolByte;
		}

		/// <summary>
		/// Gets an IP address from a byte array.  The IP address
		/// is assumed to be in the correct byte order as read from 
		/// left to right.
		/// </summary>
		/// <param name="bytes">The bytes to extract the value from.</param>
		/// <param name="index">The starting point to look for the
		/// IP address.</param>
		/// <returns>The IP address extracted from the byte array.</returns>
		static IPAddress GetIPAddress( byte[ ] bytes, int index )
		{
			Debug.Assert( bytes.Length >= ( index + IPPacket.IPAddressLength ),
				"Not enough packet bytes to extract IP address." );
	
			IPAddress Address = null;
	
			// Get the IP address bytes.
	
			byte[ ] IPAddressBytes =
				Packet.GetBytesFromByteArray( bytes, 
				index, 
				IPPacket.IPAddressLength ); 
	
			// Convert the bytes into an IP address.
			//
			// For some reason using the above byte array and trying to
			// create a new IPAddress object throws an ArgumentException so
			// for now we create an IP address dotted quad string and use that
			// instead.
	
			string IPAddressString = GetIPAddressStringFromBytes( IPAddressBytes );
			Address = IPAddress.Parse( IPAddressString );
	
			return Address;
		}

		/// <summary>
		/// Gets an IP address string of the form "A.B.C.D".
		/// </summary>
		/// <param name="bytes">The byte array to get the IP address from.
		/// </param>
		/// <returns>An IP address string.</returns>
		static string GetIPAddressStringFromBytes( byte[ ] bytes )
		{
			StringBuilder Address = new StringBuilder( );
	
			for( int i = 0; i < IPPacket.IPAddressLength; ++i )
			{
				Address.Append( bytes[ i ].ToString( CultureInfo.InvariantCulture ) );
				
				if( ( i + 1 ) < IPPacket.IPAddressLength )
				{
					Address.Append( "." );
				}
			}
	
			return Address.ToString( );
		}

	}
}
